CPP|自定义伪随机数和标准库中的随机数

生成随机数的能力在某些类型的程序中非常有用,特别是在游戏、统计建模程序和需要对随机事件建模的科学模拟中。以游戏为例——如果没有随机事件,怪物总是以同样的方式攻击你,你总是会找到同样的宝藏,地牢的布局永远不会改变,等等……这不会成为一个很好的游戏。

1 自定义伪随机数

基本思路是通过设置种子数、组合乘法、加法和迭代,再通过循环求余便可以得出一些简单的随机数。

#include <iostream>

unsigned int PRNG()

{

// our initial starting seed is 5323

static unsigned int seed = 5323;

// Take the current seed and generate a new value from it

// Due to our use of large constants and overflow, it would be

// hard for someone to casually predict what the next number is

// going to be from the previous one.

seed = 8253729 * seed + 2396403;

// Take the seed and return a value between 0 and 32767

return seed % 32768;

}

int main()

{

// Print 20 random numbers

for (int count=1; count <= 20; ++count)

{

std::cout << PRNG()*100/32768 << "\t";

// If we've printed 5 numbers, start a new row

if (count % 5 == 0)

std::cout << "\n";

}

system("pause");

return 0;

}

/*

23 4 67 22 77

44 30 45 99 0

59 85 87 74 56

42 42 65 20 17

*/

2 标准库中的随机数函数

标准库中有两个函数用于产生随机数:

srand(int); //设置起始种子值

rand();// 产生随机数

#include <iostream>

#include <stdlib.h> // for std::rand() and std::srand()

int main()

{

srand(5323); // set initial seed value to 5323

// Print 20 random numbers

for (int count=1; count <= 20; ++count)

{

std::cout << 100 + rand()/100 << "\t";

// If we've printed 5 numbers, start a new row

if (count % 5 == 0)

std::cout << "\n";

}

system("pause");

return 0;

}

/*

274 185 294 113 369

177 381 252 278 169

149 104 226 387 307

418 337 391 258 399

*/

3 选择随机性更强的随机种子(时间)

头文件中的time_t time(time_t *seconds)?返回自纪元 Epoch(1970-01-01 00:00:00 UTC)起经过的时间,以秒为单位。如果?seconds?不为空,则返回值也存储在变量?seconds?中。可以以这个秒数做为随机数的种子值。

#include <iostream>

#include <cstdlib> // for std::rand() and std::srand()

#include <ctime> // for std::time()

int main()

{

srand(static_cast(time(0)));

// set initial seed value to system clock

for (int count=1; count <= 20; ++count)

{

std::cout << rand()%100 << "\t";

// If we've printed 5 numbers, start a new row

if (count % 5 == 0)

std::cout << "\n";

}

system("pause");

return 0;

}

/*

70 22 48 60 15

87 41 20 56 0

51 7 68 37 4

98 62 96 80 18

*/

4 在两个任意值之间生成随机数

#include <iostream>

#include <time.h>

using namespace std;

// Generate a random number between min and max (inclusive)

// Assumes srand() has already been called

// Assumes max - min <= RAND_MAX

int getRandomNumber(int min, int max)

{

double fraction = 1.0 / (RAND_MAX + 1.0);

// evenly distribute the random number across our range

return min + static_cast((max - min + 1) * (rand() * fraction));

}

int main()

{

srand(time(0));

for(int i=1; i<21; i++)

{

cout<<getRandomNumber(100,200)<<" ";

if(i%5==0)

cout<<endl;

}

system("pause");

return 0;

}

/*

184 172 153 106 197

111 156 167 200 169

124 105 167 185 113

171 144 104 176 192

*/

为什么我们在上面的函数中使用除法而不是模。简而言之,模方法倾向于偏向于低数值。

让我们考虑一下如果上面的函数是这样的话会发生什么:

min+(std::rand()%(max min+1));

看起来很相似,对吧让我们来看看哪里出了问题。为了简化这个例子,假设rand()总是返回一个介于0和9(包括0和9)之间的随机数,对于我们的示例,我们将选择min=0,max=6因此,max-min+1是7。

现在让我们计算所有可能的结果:

0 + (0 % 7) = 0

0 + (1 % 7) = 1

0 + (2 % 7) = 2

0 + (3 % 7) = 3

0 + (4 % 7) = 4

0 + (5 % 7) = 5

0 + (6 % 7) = 6

0 + (7 % 7) = 0

0 + (8 % 7) = 1

0 + (9 % 7) = 2

看看结果的分布,结果0到2出现两次,而3到6只出现一次。这种方法明显偏向于较小的值,通过扩展,大多数涉及该算法的情况都会有类似的行为。

现在让我们看看上面getRandomNumber()函数的结果,使用与上面相同的参数(rand()返回一个介于0和9(包括0和9)、min=0和max=6)之间的数字)在这种情况下,分数=1/(9+1)=0.1max-min+1仍然是7。

如果不用求模,而是用除法:

return min + (std::rand() % (max-min+1));

可能的结果:

0 + static_cast(7 * (0 * 0.1))) = 0 + static_cast(0) = 0

0 + static_cast(7 * (1 * 0.1))) = 0 + static_cast(0.7) = 0

0 + static_cast(7 * (2 * 0.1))) = 0 + static_cast(1.4) = 1

0 + static_cast(7 * (3 * 0.1))) = 0 + static_cast(2.1) = 2

0 + static_cast(7 * (4 * 0.1))) = 0 + static_cast(2.8) = 2

0 + static_cast(7 * (5 * 0.1))) = 0 + static_cast(3.5) = 3

0 + static_cast(7 * (6 * 0.1))) = 0 + static_cast(4.2) = 4

0 + static_cast(7 * (7 * 0.1))) = 0 + static_cast(4.9) = 4

0 + static_cast(7 * (8 * 0.1))) = 0 + static_cast(5.6) = 5

0 + static_cast(7 * (9 * 0.1))) = 0 + static_cast(6.3) = 6

这里的偏向仍然是稍微朝着较低的数字(0、2和4出现两次,而1、3、5和6出现一次),但它的分布要均匀得多。

尽管getRandomNumber()比模方法更难理解,但我们还是提倡使用division方法,因为它产生的结果偏差较小。

5 使用随机库

一个可能更好的解决方案是使用第三方库来为您处理所有这些内容。

-End-

本页共164段,5440个字符,7108 Byte(字节)